package com.appspot.moviesessions.infrastructure.persistence;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.jdo.Extent;
import javax.jdo.PersistenceManager;
import javax.jdo.PersistenceManagerFactory;
import javax.jdo.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Repository;

import com.appspot.moviesessions.model.Location;
import com.appspot.moviesessions.model.Movie;
import com.appspot.moviesessions.model.Movie.Fields;
import com.appspot.moviesessions.model.MovieCreatedEvent;
import com.appspot.moviesessions.model.MovieInformationFoundEvent;
import com.appspot.moviesessions.model.MovieSessionFoundEvent;
import com.appspot.moviesessions.model.Session;
import com.appspot.moviesessions.model.SessionImpl;
import com.appspot.moviesessions.model.SessionRepository;
import com.google.appengine.api.blobstore.BlobKey;
import com.google.appengine.api.blobstore.BlobstoreService;
import com.google.appengine.api.datastore.Key;

/*
 * 
 * http://localhost:8888/_ah/admin/
 * 
 * */
@Repository("sessionRepository")
public class JDOSessionRepository implements SessionRepository,
		ApplicationListener<ApplicationEvent> {

	private PersistenceManagerFactory persistenceManagerFactory;

	private Map<Class, EventHandler> handlers = new HashMap<Class, JDOSessionRepository.EventHandler>();

	private ApplicationEventPublisher eventPublisher;

	private BlobstoreService blobStoreService;

	private static final Log LOG = LogFactory
			.getLog(JDOSessionRepository.class);

	@Autowired
	public JDOSessionRepository(
			PersistenceManagerFactory persistenceManagerFactory,
			ApplicationEventPublisher publisher,
			BlobstoreService blobStoreService) {
		this.persistenceManagerFactory = persistenceManagerFactory;
		this.eventPublisher = publisher;
		this.blobStoreService = blobStoreService;
		// TODO: extract event multiplexer
		handlers.put(MovieSessionFoundEvent.class, new EventHandler() {
			@Override
			public void handle(ApplicationEvent event) {
				onSessionFound((MovieSessionFoundEvent) event);
			}
		});
		handlers.put(MovieInformationFoundEvent.class, new EventHandler() {

			@Override
			public void handle(ApplicationEvent event) {
				onMovieInformationFound((MovieInformationFoundEvent) event);

			}
		});
	}

	public void purge() {
		PersistenceManager persistenceManager = persistenceManagerFactory
				.getPersistenceManager();

		Class[] entities = new Class[] { SessionRecord.class,
				LocationRecord.class };
		for (Class entity : entities) {
			Query q = persistenceManager.newQuery(entity);
			q.deletePersistentAll();
		}

		Extent<MovieRecord> extent = persistenceManager.getExtent(
				MovieRecord.class, false);
		List<BlobKey> imageKeys = new ArrayList<BlobKey>();
		for (MovieRecord movieRecord : extent) {
			if (movieRecord.getPosterKey() != null) {
				imageKeys.add(new BlobKey(movieRecord.getPosterKey()));
			}
			persistenceManager.deletePersistent(movieRecord);
		}
		blobStoreService.delete(imageKeys.toArray(new BlobKey[0]));
	}

	@Override
	public Collection<Session> getCurrentSessions() {
		PersistenceManager persistenceManager = persistenceManagerFactory
				.getPersistenceManager();
		Map<Key, Movie> movieCache = new HashMap<Key, Movie>();
		Map<Key, Location> locationCache = new HashMap<Key, Location>();
		try {
			List<Session> sessions = new ArrayList<Session>();
			Extent<SessionRecord> extent = persistenceManager.getExtent(
					SessionRecord.class, false);
			for (SessionRecord sessionRecord : extent) {
				Key movieKey = sessionRecord.getMovie();
				Key locationKey = sessionRecord.getLocation();
				if (!movieCache.containsKey(movieKey)) {
					movieCache.put(
							movieKey,
							getByKey(persistenceManager, MovieRecord.class,
									movieKey));
				}
				if (!locationCache.containsKey(locationKey)) {
					locationCache.put(
							locationKey,
							getByKey(persistenceManager, LocationRecord.class,
									locationKey));
				}
				sessions.add(new SessionImpl(movieCache.get(movieKey),
						sessionRecord.getTime(), locationCache.get(locationKey)));
			}
			extent.closeAll();

			return sessions;
		} finally {
			persistenceManager.close();
		}
	}

	private <T> T getByKey(PersistenceManager manager, Class<T> type, Key key) {
		return manager.getObjectById(type, key);
	}

	private <T> T getByName(PersistenceManager persistenceManager,
			Class<T> type, String name) {

		Query query = persistenceManager.newQuery(type, "name == vName");
		query.declareParameters("String vName");
		Collection<T> results = (Collection<T>) query.execute(name);
		return (results.size() > 0) ? results.iterator().next() : null;

	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		Class<? extends ApplicationEvent> key = event.getClass();
		if (handlers.containsKey(key)) {
			handlers.get(key).handle(event);
		}
	}

	public void onMovieInformationFound(MovieInformationFoundEvent event) {
		PersistenceManager persistenceManager = persistenceManagerFactory
				.getPersistenceManager();
		try {
			String movieName = event.getName();
			MovieRecord movie = getByName(persistenceManager,
					MovieRecord.class, movieName);
			if (movie == null) {
				LOG.error(movieName + " not found");
				return;
			}
			Map<Fields, Object> eventFields = event.getFields();
			movie.update(eventFields);
			persistenceManager.makePersistent(movie);
		} finally {
			persistenceManager.close();
		}
	}

	public void onSessionFound(MovieSessionFoundEvent event) {
		PersistenceManager persistenceManager = persistenceManagerFactory
				.getPersistenceManager();
		try {
			String movieName = event.getName();
			String locationName = event.getLocation();
			MovieRecord movie = getByName(persistenceManager,
					MovieRecord.class, movieName);
			LocationRecord location = getByName(persistenceManager,
					LocationRecord.class, locationName);
			SessionRecord session = null;

			if (movie == null) {
				movie = persistenceManager.makePersistent(new MovieRecord(
						movieName));
				eventPublisher.publishEvent(new MovieCreatedEvent(this,
						movieName));

			}
			if (location == null) {
				location = persistenceManager
						.makePersistent(new LocationRecord(locationName));
			}
			session = new SessionRecord(movie.getKey(), event.getTime(),
					location.getKey());
			persistenceManager.makePersistent(session);
		} finally {
			persistenceManager.close();
		}

	}

	private interface EventHandler {
		void handle(ApplicationEvent event);
	}
}
